#include <LitStdAfx.h>
#include "LitSceneManager.h"

/*
        SHADOWTYPE_NONE = 0x00,
		SHADOWDETAILTYPE_ADDITIVE = 0x01,
		SHADOWDETAILTYPE_MODULATIVE = 0x02,
		SHADOWDETAILTYPE_INTEGRATED = 0x04,
		SHADOWDETAILTYPE_STENCIL = 0x10,
		SHADOWDETAILTYPE_TEXTURE = 0x20
        SHADOWTYPE_STENCIL_MODULATIVE = 0x12,
        SHADOWTYPE_STENCIL_ADDITIVE = 0x11,
        SHADOWTYPE_TEXTURE_MODULATIVE = 0x22
        SHADOWTYPE_TEXTURE_ADDITIVE = 0x21,
		SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED = 0x25,
		SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED = 0x26
*/
namespace Lit
{
	LIT_API std::string trShadowTechnique( Ogre::ShadowTechnique tech ){
		switch( tech )
		{
			case Ogre::SHADOWTYPE_NONE: return "SHADOWTYPE_STENCIL_MODULATIVE";
			case Ogre::SHADOWTYPE_STENCIL_ADDITIVE: return "SHADOWTYPE_STENCIL_ADDITIVE";
			case Ogre::SHADOWTYPE_TEXTURE_MODULATIVE: return "SHADOWTYPE_TEXTURE_MODULATIVE";
			case Ogre::SHADOWTYPE_TEXTURE_ADDITIVE: return "SHADOWTYPE_TEXTURE_ADDITIVE";
			case Ogre::SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED: return "SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED";
			case Ogre::SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED: return "SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED";
			default: return "SHADOWTYPE_NONE";
		};
		return "SHADOWTYPE_NONE";
	}
	LIT_API Ogre::ShadowTechnique enumShadowTechnique( const std::string& tech ){
		if( tech == "SHADOWTYPE_STENCIL_MODULATIVE" ) return Ogre::SHADOWTYPE_STENCIL_MODULATIVE;
		else if( tech == "SHADOWTYPE_STENCIL_ADDITIVE" ) return Ogre::SHADOWTYPE_STENCIL_ADDITIVE;
		else if( tech == "SHADOWTYPE_TEXTURE_MODULATIVE" ) return Ogre::SHADOWTYPE_TEXTURE_MODULATIVE;
		else if( tech == "SHADOWTYPE_TEXTURE_ADDITIVE" ) return Ogre::SHADOWTYPE_TEXTURE_ADDITIVE;
		else if( tech == "SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED" ) return Ogre::SHADOWTYPE_TEXTURE_ADDITIVE_INTEGRATED;
		else if( tech == "SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED" ) return Ogre::SHADOWTYPE_TEXTURE_MODULATIVE_INTEGRATED;
		return Ogre::SHADOWTYPE_NONE;
	}
	SceneManager::SceneManager( Ogre::SceneManager * mgr )
		: mSceneMgr(mgr), CoreComponent(COM_SCENEMANAGER)
	{ 
		mSelection = new SceneSelection();
		mMovables.addListener( this ); 
		mXmlMgr = new XmlSceneManager(mgr);
	}
	SceneManager::~SceneManager( ){  clearScene(); mXmlMgr.destroy(); mSelection.destroy();  }
	
	
	void SceneManager::initialise(OgreRenderWindowPtr win, OgreCameraPtr cam, OgreSceneManagerPtr mgr ){ 
		mCamera = cam; 
		mWindow=win; 
		mSceneMgr=mgr; 

		mXmlMgr->initialise( win, cam, mgr ); 
	}
	void SceneManager::shutdown(){ 
		clearScene(); 
		mXmlMgr->shutdown(); 
	}

	bool SceneManager::renderOneFrame( float dt )
	{
		return true;
	}
	std::string SceneManager::baseMeshName( const std::string &raw_mesh ) const
	{
		return Ogre::StringUtil::replaceAll(raw_mesh, ".mesh", "");
	}

	std::string SceneManager::nextLightName( const std::string &light_name) const{
		std::string nam = baseMeshName((light_name.empty()?"Light_":(light_name+"_")));
		Ogre::NameGenerator g(nam);
		while( mSceneMgr->hasLight((nam=g.generate())) ) ;
		return nam;
	}
	void	SceneManager::setSelected( MovablePtr obj, bool state ){
		mSelection->setSelected( obj, state );
	}
	void SceneManager::destroyQuery( Ogre::SceneQuery * query )
	{
		if( query != NULL )
			mSceneMgr->destroyQuery( query );
		query = NULL;
	}
	XmlSceneManagerPtr							SceneManager::xmlSceneManager(){ return mXmlMgr; }
	OgreSceneManagerPtr&						SceneManager::_sceneManager(){ return mSceneMgr; }
	Ogre::RaySceneQuery*						SceneManager::createRayQuery( const Ogre::Ray& ray, unsigned long mask ){ return mSceneMgr->createRayQuery( ray, mask ); }
	Ogre::PlaneBoundedVolumeListSceneQuery*		SceneManager::createVolumeQuery( const Ogre::PlaneBoundedVolume& vol, unsigned long mask ){ Ogre::PlaneBoundedVolumeList lst; lst.push_back( vol ); return mSceneMgr->createPlaneBoundedVolumeQuery( lst, mask ); }
	
	bool										SceneManager::hasMovable( const std::string &name ) const{
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
			if( (*i)->getName() == name ) return true;
		return false;
	}
	
	void SceneManager::selectAll( ){
		if( !mSelection ) return;
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
		{
			mSelection->setSelected( *(*i).ref(), true );
		}
	}
	void SceneManager::selectInverse( ){
		if( !mSelection ) return;
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
		{
			if( mSelection->isSelected( *(*i).ref() ) ) mSelection->setSelected( *(*i).ref(), false );
			else mSelection->setSelected( *(*i).ref(), true );
		}
	}
	void SceneManager::selectNone( ){
		if( !mSelection ) return;
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
		{
			mSelection->setSelected( *(*i).ref(), false );
		}
	}
	SceneSelectionPtr& SceneManager::getSelection(){ return mSelection; }
	const SceneSelectionPtr& SceneManager::getSelection() const{ return mSelection; }
	void SceneManager::setSelection( const SceneSelectionPtr& ptr ){ mSelection.destroy(); mSelection=ptr; }

	std::string SceneManager::nextEntityName( const std::string &raw_mesh) const{
		std::string nam = baseMeshName(raw_mesh)+"_";
		Ogre::NameGenerator g(nam);
		while( mSceneMgr->hasEntity((nam=g.generate())) ) ;
		return nam;
	}
	template<typename ResTypePtr>
	ResTypePtr SceneManager::_createMovable( typename ResTypePtr::Ptr r, Ogre::SceneManager* sm  )
	{
		MovableMasterPtr master(r);
		ResTypePtr ret(r);
		ret->create( sm?sm:mSceneMgr );
		mMovables.push( master );
		LIT_LOG("["+ret->getMovableTypeStr()+"] '"+r->getName()+"' created");
		setMovableProperties(r);
		return ret;
	}
	bool SceneManager::isPhysicsActor( MovablePtr mov ) const{
		return Ogre::StringConverter::parseBool(mov->getData<std::string>( "IsPhysicsActor", Ogre::StringConverter::toString(bool(false)), false ));
	}
	Physics::BodyPtr SceneManager::getPhysicsBody( MovablePtr mov ){
		return mov->getData<Physics::Body>( "Body", NULL );
	}
	void SceneManager::setMovableProperties( MovablePtr mov, bool is_phys_actor, Physics::BodyPtr body ){
		mov->setData<std::string>("IsPhysicsActor", Ogre::StringConverter::toString( is_phys_actor ) );
		mov->setData<Physics::Body>("Body", body?*body:NULL );
	}
	Physics::BodyPtr	SceneManager::createDynamicBody( Physics::WorldPtr world, const std::string &mesh, Lit::Real mass, Physics::ShapeType shape, Ogre::SceneNode * parentNode, Ogre::SceneManager* sm, const Lit::Vec3 & position, const Lit::Vec3 &scale, const Lit::Quat& orientation ){
		return createDynamicBody( world, nextEntityName(mesh), mesh, mass, shape, parentNode, sm, position, scale, orientation );
	}
	Physics::BodyPtr	SceneManager::createDynamicBody( Physics::WorldPtr world, const std::string &name, const std::string &mesh, Lit::Real mass, Physics::ShapeType shape, Ogre::SceneNode * parentNode, Ogre::SceneManager* sm, const Lit::Vec3 & position, const Lit::Vec3 &scale, const Lit::Quat& orientation ){
		EntityPtr ent(createEntity( name, mesh, parentNode, sm, position, scale, orientation ));
		Physics::BodyPtr ret;
		if( mass == 0 ) throw Lit::RuntimeError( Lit::EXC_RUNTIME_ERROR, "Cannot create dynamic getEntity '"+name+"' of null mass, should be static body", EXC_LOC );
		else ret=Physics::BodyPtr(world->createBody( mass, *ent, shape, 0 ));
		setMovableProperties( *ent, true, ret );
		return ret;
	}
	Physics::BodyPtr	SceneManager::createStaticBody( Physics::WorldPtr world, const std::string &mesh, Physics::ShapeType shape, Ogre::SceneNode * parentNode, Ogre::SceneManager* sm, const Lit::Vec3 & position, const Lit::Vec3 &scale, const Lit::Quat& orientation ){
		return createStaticBody( world, nextEntityName(mesh), mesh, shape, parentNode, sm, position, scale, orientation );
	}
	Physics::BodyPtr	SceneManager::createStaticBody( Physics::WorldPtr world, const std::string &name, const std::string &mesh, Physics::ShapeType shape, Ogre::SceneNode * parentNode, Ogre::SceneManager* sm, const Lit::Vec3 & position, const Lit::Vec3 &scale, const Lit::Quat& orientation ){
		EntityPtr ent(createEntity( name, mesh, parentNode, sm, position, scale, orientation ));
		Physics::BodyPtr ret(Physics::BodyPtr(world->createStaticBody( *ent, shape, 0, position, orientation )));
		setMovableProperties( *ent, true, ret );
		return ret;
	}
	Physics::BodyPtr	SceneManager::createDynamicBody( Physics::WorldPtr world, EntityPtr getEntity, Lit::Real mass, Physics::ShapeType shape, Ogre::SceneNode * parentNode, Ogre::SceneManager* sm, const Lit::Vec3 & position, const Lit::Vec3 &scale, const Lit::Quat& orientation ){
		getEntity->setPosition( position );
		getEntity->setScale( scale );
		getEntity->setOrientation( orientation );
		Physics::BodyPtr ret(Physics::BodyPtr(world->createBody( mass, *getEntity, shape, 0 )));
		setMovableProperties( *getEntity, true, ret );
		return ret;
	}
	Physics::BodyPtr	SceneManager::createStaticBody( Physics::WorldPtr world, EntityPtr getEntity, Physics::ShapeType shape, Ogre::SceneNode * parentNode, Ogre::SceneManager* sm, const Lit::Vec3 & position, const Lit::Vec3 &scale, const Lit::Quat& orientation ){
		getEntity->setPosition( position );
		getEntity->setScale( scale );
		getEntity->setOrientation( orientation );
		Physics::BodyPtr ret(Physics::BodyPtr(world->createStaticBody( *getEntity, shape, 0 )));
		setMovableProperties( *getEntity, true, ret );
		return ret;
	}
	MeshDescriptionFilePtr SceneManager::getMeshDescription( const std::string &mesh_name ){
		std::string descName(mesh_name);
		descName = Ogre::StringUtil::replaceAll( mesh_name, ".mesh", "" );
		descName += ".meshdef";
		if( MeshDescriptionManager::getSingletonPtr()->resourceExists( descName ) )
			return MeshDescriptionFilePtr (MeshDescriptionManager::getSingleton().getByName(descName));
		return MeshDescriptionFilePtr ();
	}
	MeshDescriptionFilePtr SceneManager::getMeshDescription( Ogre::MeshPtr mesh ){
		if( mesh.isNull() ) return MeshDescriptionFilePtr();
		return getMeshDescription( mesh->getName() );
	}

	Physics::BodyPtr SceneManager::createActor( const std::string &name, const MeshDescriptionFilePtr& desc, Physics::WorldPtr world, const Lit::Vec3& scale, Ogre::SceneNode * parentNode, Ogre::SceneManager* sm )
	{
		try
		{
			if( hasEntity(name) )
				throw ItemDuplicate( "Entity<"+name+">", "SceneManager", EXC_LOC );
			Physics::BodyPtr ret;
			if( desc->isDynamic() )
				ret = createDynamicBody( world, desc->getMesh(), desc->getMass(), desc->getShape(), parentNode, sm, desc->getOffset(), scale );
			else ret = createStaticBody( world, desc->getMesh(), desc->getShape(), parentNode, sm, desc->getOffset(), scale );
			if( !desc->getMaterial().empty() ) ret->setMaterialName( desc->getMaterial() );
			return ret;
		}
		catch( std::exception & e )
		{
			Ogre::LogManager::getSingleton().logMessage( Ogre::LML_CRITICAL, "Cannot create getEntity '"+name+"' because:\r\n"+std::string(e.what()) );
			throw;
		}
		return NULL;
	}
	EntityPtr SceneManager::createEntity( const std::string &mesh, Ogre::SceneNode * parentNode, Ogre::SceneManager* sm, const Lit::Vec3 & position, const Lit::Vec3 &scale, const Lit::Quat& orientation )
	{
		return createEntity( nextEntityName(mesh), mesh, parentNode, sm, position, scale, orientation );
	}
	EntityPtr SceneManager::createEntity( const std::string &name, const std::string &mesh, Ogre::SceneNode * parentNode, Ogre::SceneManager* sm, const Lit::Vec3 & position, const Lit::Vec3 &scale, const Lit::Quat& orientation ){
		try
		{
			if( hasEntity(name) )
				throw ItemDuplicate( "Entity<"+name+">", "SceneManager", EXC_LOC );
			EntityPtr getEntity( _createMovable<EntityPtr>(new Entity(name, mesh, parentNode), sm) );
			getEntity->setPosition( position );
			getEntity->setScale( scale );
			getEntity->setOrientation( orientation );
			return getEntity;
		}
		catch( std::exception & e )
		{
			Ogre::LogManager::getSingleton().logMessage( Ogre::LML_CRITICAL, "Cannot create getEntity '"+name+"' because:\r\n"+std::string(e.what()) );
			throw;
		}
		return NULL;
	}
	MovableMasterPtrList::Itor SceneManager::_find( const std::string &name, MovableType type ){
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
		{
			if( (*i) && i->ref()->getName() == name && i->ref()->getMovableType() == type )
			{
				return i;
			}
		}
		return mMovables.end();
	}
	MovableMasterPtrList::ConstItor SceneManager::_find( const std::string &name, MovableType type ) const{
		for( MovableMasterPtrList::ConstItor i=mMovables.begin(); i!=mMovables.end(); i++ )
		{
			if( *i && (i->get()->getName() == name) && (i->get()->getMovableType() == type) )
			{
				return i;
			}
		}
		return mMovables.end();
	}
	void SceneManager::removeMovable( Movable* mov ){
		if( !mov ) return;
		try
		{
			std::string name = mov->getName();
			MovableType type = mov->getMovableType();

			switch( mov->getMovableType() )
			{
			case MT_ENTITY: ((Entity*)mov)->destroy(); break;
			case MT_LIGHT: ((Light*)mov)->destroy(); break;
			case MT_SOUND: /*((Sound*)mov)->destroy();*/ break;
			case MT_PLANE: ((Plane*)mov)->destroy(); break;
			case MT_UNDEFINED: 
			default:
				throw Lit::ImplementationMissing( "SceneManager::removeMovable("+std::string(trMovableType(MT_UNDEFINED))+"<"+mov->getName()+">)", EXC_LOC );
			};
			LIT_LOG("["+mov->getMovableTypeStr()+"] '"+name+"' destroyed");
		
		mMovables.erase( _find( name, type ) );
		}
		catch( std::exception & e )
		{
			Ogre::LogManager::getSingleton().logMessage( Ogre::LML_CRITICAL, "Cannot remove getEntity '"+mov->getName()+"' because:\r\n"+std::string(e.what()) );
			throw;
		} 
		
	}
	void SceneManager::removeEntity( const std::string &name ){
		if( !hasEntity( name ) )
			throw Lit::ItemMissing( "Entity<"+name+">", "SceneManager", EXC_LOC );
		removeMovable( *getEntity(name) );
	}
	void SceneManager::removeLight( const std::string &name ){
		if( !hasLight( name ) )
			throw Lit::ItemMissing( "Light<"+name+">", "SceneManager", EXC_LOC );
		removeMovable( *getLight(name) );
	}
	void SceneManager::removePlane( const std::string &name ){
		if( !hasPlane( name ) )
			throw Lit::ItemMissing( "Plane<"+name+">", "SceneManager", EXC_LOC );
		removeMovable( *getPlane(name) );
	}
	LightPtr SceneManager::createLight( const std::string &name, Light::Type type_, const Ogre::Vector3& position, const Ogre::Vector3& direction ){
		try
		{
			if( hasEntity(name) )
				throw ItemDuplicate( "Light<"+name+">", "SceneManager", EXC_LOC );

			return _createMovable<LightPtr>(new Light(name, type_, position, direction));
		}
		catch( std::exception & e )
		{
			Ogre::LogManager::getSingleton().logMessage( Ogre::LML_CRITICAL, "Cannot create light '"+name+"' because:\r\n"+std::string(e.what()) );
			throw;
		}
		return NULL;
	}
	PlanePtr SceneManager::createPlane( PlaneAxis axis, const std::string &name, const Vec2 &size ){
		try
		{
			if( hasPlane(name) )
				throw ItemDuplicate( "Light<"+name+">", "SceneManager", EXC_LOC );

			return _createMovable<PlanePtr>(new Plane(axis, name, size));
		}
		catch( std::exception & e )
		{
			Ogre::LogManager::getSingleton().logMessage( Ogre::LML_CRITICAL, "Cannot create plane '"+name+"' because:\r\n"+std::string(e.what()) );
			throw;
		}
		return NULL;
	}
	void SceneManager::clearSelection( bool unselect ){
		if( mSelection ) mSelection->clearSelection(unselect);
	}
	PlanePtr SceneManager::getPlane(const std::string &name){ 
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
		{
			if( (*i)->getMovableType() == MT_PLANE && (*i)->getName() == name ){
				return ((Plane*)(*(*i).ref()));
			}
		}
		throw ItemMissing("Plane "+name, "SceneManager", EXC_LOC );
		return NULL;
	}	
	bool SceneManager::hasPlane( const std::string &name ) const{
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
			if( (*i)->getMovableType() == MT_PLANE && (*i)->getName() == name ) return true;
		return false;
	}
	bool SceneManager::hasLight( const std::string &name ) const{
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
			if( (*i)->getMovableType() == MT_LIGHT && (*i)->getName() == name ) return true;
		return false;
	}
	LightPtr SceneManager::getLight(const std::string &name){ 
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
		{
			if( (*i)->getMovableType() == MT_LIGHT && (*i)->getName() == name ){
				return ((Light*)(*(*i).ref()));
			}
		}
		throw ItemMissing("Light "+name, "SceneManager", EXC_LOC );
		return NULL;
	}
	
	MovablePtr SceneManager::getMovable( const std::string &name ){
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
			if( (*i)->getName()==name )
				return (*i).ref();
		throw ItemMissing("Movable "+name, "SceneManager", EXC_LOC );
		return NULL;
	}
	void SceneManager::clearScene(){ 
		if( mSelection ) mSelection.destroy();
		mSelection = new SceneSelection();
		mMovables.clear();
		if( mSceneMgr ) mSceneMgr->clearScene(); 
	}
	EntityPtr SceneManager::getEntity(const std::string &name)
	{
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
		{
			if( (*i)->getMovableType() == MT_ENTITY && (*i)->getName() == name ){
				return ((Entity*)(*(*i).ref()));
			}
		}
		throw ItemMissing("Entity "+name, "SceneManager", EXC_LOC );
		return NULL;
	}
	bool SceneManager::hasEntity( const std::string &name ) const
	{
		for( auto i=mMovables.begin(); i!=mMovables.end(); i++ )
			if( (*i)->getMovableType() == MT_ENTITY && (*i)->getName() == name ) return true;
		return false;
	}
	void SceneManager::itemAdded( MovableMasterPtrList::Itor& item )
	{
	}
	void SceneManager::itemRemoved( MovableMasterPtrList::Itor& item )
	{
		item->destroy();
	}

};